# -*- coding:utf-8 -*-
import os
import shutil
import time

import orjson
from bson import ObjectId
from loguru import logger

from config import settings, aredis
from exceptions import ModelNoExists
from ioservice.common.basic import mdb
from ioservice.common.mongo import find_many_docs, find_one_doc, insert_one_model, update_one_model, \
    count_documents as mongo_count_docs, bulk_update_models_unset_fields_though_id, insert_models, \
    bulk_update_models_through_id, check_models_exists_by_ids, aggregate_docs, find_mono_docs, count_documents
from ioservice.const import *
from ioservice.objectcode import bulk_generate_object_code, generate_object_code
from models.alumni import *
from task_utils.task_es import job_task_alumni_rank_score
from task_utils.task_jobs import job_alumni_join_act, job_update_alumni_completion_score
from utils.dbutil import convert2projection
from utils.iterutil import generate_batch
from utils.province_city import clean_province
from utils.regionutil import area2province, province2area

ALUMNI_COUNTER = "alumni"

REGION = {}


async def get_belong_by_alumni(alumni_id):
    pipe = [
        {"$match": {"person_id": ObjectId(alumni_id), "is_deleted": {"$ne": True}}},
        {"$lookup": {
            "from": "activity",
            "localField": "activity_id",
            "foreignField": "_id",
            "as": "dept"
        }},

        {"$lookup": {
            "from": "activity",
            "let": {"activity_id_ref": "$activity_id"},
            "pipeline": [
                {"$match": {"$expr": {"$eq": ["$_id", "$$activity_id_ref"]}}},
                {"$project": {"co_inst_ids": 1, "institution_id": 1, "_id": 0}}
            ],
            "as": "belonging"
        }},
        {"$project": {"belonging": 1, "_id": 0}}
    ]

    dept_list = await aggregate_docs(activity_register_col, pipe)
    rest = []
    for ins in dept_list:
        id_list = ins['belonging'][0]
        rest.append(id_list['institution_id'])
        if id_list.get("co_inst_ids"):
            rest.extend(id_list.get("co_inst_ids"))
    return set([str(d) for d in rest])


async def get_alumni(alumni_name=None, institution_ids=None, **kwargs):
    query = {}
    # 机构->获得活动->人才列表
    if institution_ids:
        col = mdb.get_collection(activity_col)
        activity_ids = await col.find({"institution_id": {"$in": [ObjectId(_id) for _id in institution_ids]}},
                                      {"_id": 1}).distinct("_id")
        col = mdb.get_collection(activity_register_col)
        # relations 亲人
        persons_ids = [item async for item in col.find({}, {"person_id": 1, "activity_id": 1, "_id": 0})]
        within_user_alumni = [person for person in persons_ids if person['activity_id'] in activity_ids]
        query['$and'] = {"_id": {"$in": within_user_alumni}}

        # 人才没有管理其他活动的情况(orphan case)
        if "-1" in institution_ids:
            query["$and"].append({"$nin": [person['person_id'] for person in persons_ids]})

    if alumni_name:
        query['alumni_name'] = alumni_name
    count = await mongo_count_docs(alumni_col, query)
    alumnis = await find_many_docs(alumni_col, query, skip=kwargs.get("skip", 0), limit=kwargs.get("limit", 20))
    for alumni in alumnis:
        alumni['id'] = str(alumni['_id'])
        del alumni['_id']
    return {"total": count, "data": alumnis}


async def get_alumni_by_id(_id, activity_id, **kwargs):
    alumni = await find_one_doc(alumni_col, {"_id": ObjectId(_id)}, **kwargs)
    if not alumni:
        raise ModelNoExists("alumni", str(_id))
    alumni["id"] = str(alumni["_id"])
    if alumni.get("role_by") and activity_id:
        role_list = alumni['role_by']
        role_by_act = [r for r in role_list if r['activity_id'] == activity_id]
        alumni['role_and_levels'] = role_by_act[0] if role_by_act else {}
    if "role_by" in alumni:
        del alumni['role_by']

    # [see] deprecated
    # if not alumni.get("native_place") and alumni.get("native_place", {}).get("province"):
    #     alumni["native_place"]["area"] = province2area.get(alumni.get("native_place", {}).get("province"), None)
    del alumni["_id"]
    if activity_id:
        activity_register = await find_one_doc(activity_register_col,
                                               {"activity_id": ObjectId(activity_id), "person_id": ObjectId(_id)})
        if activity_register and "role_and_levels" in alumni:
            alumni["role_and_levels"]["images"] = activity_register.get("images")
            alumni["role_and_levels"]["videos"] = activity_register.get("videos")
            alumni["role_and_levels"]["appendixes"] = activity_register.get("appendixes")
            alumni["role_and_levels"]["relationship_labels_activity_persons"] = activity_register.get(
                "relationship_labels_activity_persons")

    return alumni


async def get_alumni_by_query(query, **kwargs):
    alumnis = await find_many_docs(alumni_col, query, **kwargs)
    for alumni in alumnis:
        alumni["id"] = str(alumni["_id"])
        del alumni["_id"]
    return alumnis


async def crud_validate_email(query):
    if query:
        query["is_deleted"] = {"$ne": True}
        alumni_ins = await find_one_doc(alumni_col, query)
        if alumni_ins:
            return {"status": True, "person": str(alumni_ins["_id"])}
        else:
            return {"status": False, "person": None}

    return {"status": False, "person": None}


async def parse_alumni_context(context):
    exclude_fields = BaseSearchModel.__fields__.keys()
    query = context.dict(exclude=exclude_fields, exclude_unset=True)
    if context.query:
        if 'ids' in context.query:
            query['_id'] = {"$in": [ObjectId(i) for i in context.query.pop("ids")]}
        query.update(context.query)
    if not context.is_deleted:
        query['is_deleted'] = {"$ne": True}
    if context.name:
        if not query.get("$and"):
            query["$and"] = []
        query["$and"].append({"$or": [{"name": {"$regex": f".*{context.name}.*", "$options": "$i"}},
                                      {"name": {"$regex": f".*{context.name}.*", "$options": "$i"}}]})
        del query['name']
    if context.fields and "cur_institution_id" not in context.fields:
        context.fields.append("cur_institution_id")
    if context.region:
        if context.region not in area2province:
            raise InvalidParam("region", "not correct region")
        if not query.get("$and"):
            query["$and"] = []
        # query["native_place.province"] = {"$in": list(area2province[context.region])}
        query["$and"].append({"native_place.province": {"$in": list(area2province[context.region])}})
        del query["region"]

    if context.cur_institution_name:
        query['cur_institution_name'] = {"$regex": f".*{context.cur_institution_name}.*", "$options": "$i"}

    # todo 注释掉
    # if context.cur_institution_name:
    #     del query['cur_institution_name']
    #     # 如果搜索校友的机构，先搜机构，然后将机构id加入搜索条件中
    #     institutions = await presearch_alumni_institution(context.cur_institution_name)
    #     if institutions and not query.get("cur_institution_id"):
    #         query.update({"cur_institution_id": {"$in": [str(institution["_id"]) for institution in institutions]}})

    return query, context


async def presearch_alumni_institution(institution_name):
    institution_query = {"institution_name": {"$regex": f".*{institution_name}.*", "$options": "$i"}}
    institutions = await find_many_docs(institution_col, institution_query, projection=["institution_name", "_id"])
    return institutions


async def search_alumni_by_query(query, offset=0, size=20, fields=None, **kwargs):
    total = await mongo_count_docs(alumni_col, query)
    if size == 0 or total == 0:
        return total, []
    alumnis = await find_many_docs(alumni_col, query, skip=offset, limit=size, projection=fields, **kwargs)
    for r in alumnis:
        r["id"] = str(r["_id"])
        del r["_id"]
    return total, alumnis


async def search_alumni_by_agg(query, offset=0, size=10, fields=None, sorts=None):
    # aggregation query
    update_person_ids = []
    alumni_docs = await find_mono_docs(alumni_col, query=query, sort=['-_id'], offset=offset, size=size)
    for doc in alumni_docs:
        doc["id"] = str(doc["_id"])
        doc["children"] = doc.get("num_of_activities", 0)
        doc["activity_ids"] = [r.get("activity_id") for r in doc.get("role_by", []) if r.get("activity_id")]
        del doc["_id"]
        update_person_ids.append(doc["id"])

    return alumni_docs


async def search_alumni(context: AlumniSearchModel, belongs_to_inst: List[str] = None, is_view_orphan=False):
    query, context = await parse_alumni_context(context)

    if query.get("institution_id"):
        query["belongs_inst"] = query.get("institution_id")
        query.pop('institution_id')

    if belongs_to_inst:
        if "-1" in belongs_to_inst:
            query['$or'] = []
            query['$or'].append({"belongs_inst": {"$elemMatch": {"$in": belongs_to_inst}}})
            query['$or'].append({"belongs_inst": {"$in": [None, []]}})
        else:
            query["belongs_inst"] = {"$elemMatch": {"$in": belongs_to_inst}}

    if context.size == 0:
        context.size = 5000

    total = await count_documents(alumni_col, query=query)
    alumni = await search_alumni_by_agg(query, offset=context.offset, size=context.size,
                                        fields=context.fields,
                                        sorts=context.sort)

    return total, alumni


async def agg_user_has_persons(belongs_to_inst):
    """根据机构获得人才"""
    query = {"is_deleted": {"$ne": True}}

    if belongs_to_inst:
        if "-1" in belongs_to_inst:
            query['$or'] = []
            query['$or'].append({"belongs_inst": {"$elemMatch": {"$in": belongs_to_inst}}})
            query['$or'].append({"belongs_inst": {"$exists": True, "$eq": []}})
        else:
            query["belongs_inst"] = {"$elemMatch": {"$in": belongs_to_inst}}

    alumni_docs = await find_mono_docs(alumni_col, query=query, fields=['_id'])
    data = []
    for doc in alumni_docs:
        data.append(str(doc["_id"]))

    return data


def gender_aggregation():
    return {"gender": [
        {
            "$match": {
                "gender": {
                    "$in": [
                        "female",
                        "male",
                        "other"
                    ]
                }
            }
        },
        {
            "$group": {
                "_id": "$gender",
                "num": {
                    "$sum": 1
                }
            }
        }
    ]}


def region_aggregation(province_field="native_place.province"):
    return {"region": [
        {
            "$project": {
                "province": {
                    "$switch": {
                        "branches": [
                            {
                                "case": {
                                    "$in": [
                                        f"${province_field}",
                                        [
                                            "北京市",
                                            "天津市",
                                            "河北省",
                                            "山西省",
                                            "内蒙古自治区"
                                        ]
                                    ]
                                },
                                "then": "华北地区"
                            },
                            {
                                "case": {
                                    "$in": [
                                        f"${province_field}",
                                        [
                                            "黑龙江省",
                                            "吉林省",
                                            "辽宁省"
                                        ]
                                    ]
                                },
                                "then": "东北地区"
                            },
                            {
                                "case": {
                                    "$in": [
                                        f"${province_field}",
                                        [
                                            "上海市",
                                            "江苏省",
                                            "浙江省",
                                            "安徽省",
                                            "江西省",
                                            "山东省",
                                            "福建省",
                                            "台湾省"
                                        ]
                                    ]
                                },
                                "then": "华东地区"
                            },
                            {
                                "case": {
                                    "$in": [
                                        f"${province_field}",
                                        [
                                            "河南省",
                                            "湖北省",
                                            "湖南省"
                                        ]
                                    ]
                                },
                                "then": "华中地区"
                            },
                            {
                                "case": {
                                    "$in": [
                                        f"${province_field}",
                                        [
                                            "广东省",
                                            "广西壮族自治区",
                                            "海南省",
                                            "香港特别行政区",
                                            "澳门特别行政区"
                                        ]
                                    ]
                                },
                                "then": "华南地区"
                            },
                            {
                                "case": {
                                    "$in": [
                                        f"${province_field}",
                                        [
                                            "重庆市",
                                            "四川省",
                                            "贵州省",
                                            "云南省",
                                            "西藏自治区"
                                        ]
                                    ]
                                },
                                "then": "西南地区"
                            },
                            {
                                "case": {
                                    "$in": [
                                        f"${province_field}",
                                        [
                                            "陕西省",
                                            "甘肃省",
                                            "青海省",
                                            "宁夏回族自治区",
                                            "新疆维吾尔自治区"
                                        ]
                                    ]
                                },
                                "then": "西北地区"
                            }
                        ],
                        "default": "None"
                    }
                }
            }
        },
        {
            "$group": {
                "_id": "$province",
                "num": {
                    "$count": {}
                }
            }
        }
    ]}


def age_aggregation(start=25, end=155, step=10):
    return {"age": [
        {
            "$match": {
                "age": {
                    "$exists": True,
                    "$ne": None,
                    "$lte": end,
                    "$gt": start
                }
            }
        },
        {
            "$project": {
                "age": "$age"
            }
        },
        {
            "$group": {
                "_id": {
                    "$subtract": [
                        {
                            "$subtract": [
                                "$age",
                                0
                            ]
                        },
                        {
                            "$mod": [
                                {
                                    "$subtract": [
                                        "$age",
                                        5
                                    ]
                                },
                                step
                            ]
                        }
                    ]
                },
                "total": {
                    "$sum": 1
                }
            }
        }
    ]}


def institution_aggregation():
    return {
        "institution": [
            {
                "$group": {
                    "_id": "$cur_institution_id",
                    "num": {
                        "$sum": 1
                    }
                }
            }
        ]
    }


def job_title_aggregation():
    return {
        "job_title": [
            {
                "$group": {
                    "_id": "$job_title",
                    "num": {
                        "$sum": 1
                    }
                }
            }
        ]
    }


def get_multi_aggregation_query(query, aggregation_fields: List[str]):
    aggregation_query = [{"$match": query}]
    if not aggregation_fields:
        return {}
    facet = {}
    for field in aggregation_fields:
        if field == "gender":
            facet.update(gender_aggregation())
        elif field == "region":
            facet.update(region_aggregation())
        elif field == "age":
            facet.update(age_aggregation())
        elif field == "institution":
            facet.update(institution_aggregation())
        elif field == "job_title":
            facet.update(job_title_aggregation())
    aggregation_query.append({"$facet": facet})
    return aggregation_query


async def aggregate_alumni(params: AlumniSearchModel):
    query, _ = await parse_alumni_context(params)
    aggregation_query = get_multi_aggregation_query(query, params.aggs or ["gender", "region", "age", "institution",
                                                                           "job_title"])
    result = await aggregate_docs(alumni_col, aggregation_query)
    result = result[0]
    return result


def update_alumni_id_photo(person_id, identify_photo):
    person_id = str(person_id)
    if not os.path.exists(identify_photo):
        raise InvalidParam("identify_photo", "identify_photo not exists!")
    try:
        file_path = os.path.join(settings.basic['static_path'], '', 'id_photo',
                                 person_id + identify_photo.split('.')[-1])
        if not os.path.exists(os.path.dirname(file_path)):
            os.makedirs(os.path.dirname(file_path))
        shutil.copy(identify_photo, file_path)
        new_path = os.path.join(settings.basic['static_url'], '', 'id_photo',
                                person_id + identify_photo.split('.')[-1])
        return new_path
    except Exception as e:
        logger.error(f"insert id photo wrong; Exception:{e}")
        raise InvalidParam("identify_photo", "photo update error")


async def insert_alumni(alumni: AlumniInsertModel):
    # TODO 添加检查
    alumni.set_ocode(await generate_object_code(alumni_counter))
    if alumni.identify_photo:
        try:
            alumni.identify_photo = update_alumni_id_photo(alumni.ocode, identify_photo=alumni.identify_photo)
        except:
            pass
    alumni_id = await insert_one_model(alumni_col, Alumni(**alumni.dict(exclude_unset=True, exclude_none=True)))
    logger.info(f"alumni insert success::doc_id {alumni_id}; person_id {str(alumni_id)}")
    return str(alumni_id)


async def bulk_insert_alumni(data: List[AlumniInsertModel], batch_size=100):
    results = set()
    ocodes = await bulk_generate_object_code(alumni_counter, len(data))
    for ocode, alumni in zip(ocodes, data):
        alumni.set_ocode(ocode)
        if not alumni.belongs_inst:
            alumni.belongs_inst = []

        alumni.update_time = date_now()
        # clearn province
        _province = alumni.job_province
        # identify_id
        if _province:
            alumni.job_province = clean_province(_province)
        if alumni.identify_photo:
            try:
                alumni.identify_photo = update_alumni_id_photo(ocode[1], identify_photo=alumni.identify_photo)
            except:
                continue
    for batch in generate_batch(data, batch_size=batch_size):
        inserted = await insert_models(alumni_col, batch)
        if inserted:
            results.update([str(ins) for ins in inserted])
    return results


async def update_alumni_by_id(person_id, alumni: Alumni):
    """
    通过person_id更新校友信息
    """
    # TODO 新增校验
    if not person_id:
        raise ModelNoExists("alumni", "id")

    res = await find_one_doc(alumni_col, {"_id": ObjectId(person_id)}, projection=["person_id"])
    if not res:
        raise ModelNoExists("alumni", str(person_id))
    if alumni.identify_photo:
        alumni.identify_photo = update_alumni_id_photo(person_id, alumni_col)
    res = await update_one_model(alumni_col, {"_id": ObjectId(person_id)}, alumni)
    return res


async def bulk_update_alumni(alumnis: List[AlumniUpdateModel], action):
    for alumni in alumnis:
        alumni.update_time = date_now()

        if alumni.identify_photo:
            try:
                alumni.identify_photo = update_alumni_id_photo(alumni.id, alumni_col)
            except:
                continue

    res = await bulk_update_models_through_id(alumni_col, alumnis, action)
    return res


async def check_alumni_exists_by_ids(ids):
    return await check_models_exists_by_ids(alumni_col, ids)
